package dev;

/**
 * ...
 * @author Cref
 */
#if macro
import haxe.macro.Expr;
import haxe.macro.Context;
import hxtc.dom.DOMTools;
import thx.macro.Macros;
using jstm.MacroTest;
#else
import jstm.Host;
import dev.State;
#end

typedef EmbedArgs = { elm:HTMLElement, setActive:Bool->Void };
typedef EmbedReg = { tagName:String, className:String, methodName:String };

//takes care of loading embedded controls
class HTMLDisplay {
	#if !macro
	
	public function new(controller:Dynamic, state:State) {
		this.controller = controller;
		embedRegs = new Hash();
		embeddedElements = new Hash();
		this.state = state;
		fallbackIds = '';
		state.on(after(refresh),_refresh,100);
	}
	var controller:Dynamic;
	var state:State;
	
	public var element(default, setElement):HTMLElement;
	function setElement(e:HTMLElement) {
		//alleen voor IE nodig
		if (Host.engine != jstm.BrowserEngine.Trident) saveElements = function() { };
		else eWin = e.ownerDocument.getElementsByTagName('window')[0];
		liveEmbeds = cast e.getElementsByTagName('embed');//live
		element = e;
		refresh();
		return e;
	}
	var eWin:HTMLElement;
	
	//in geval van IE moeten elementen die worden hergebruikt in de DOM-structuur blijven anders raken properties verloren
	dynamic function saveElements() {
		for (e in embeddedElements) eWin.appendChild(e.elm);
		//tijdelijke oplossing tbv cimple legacy!
		for (e in element.getElementsByClassName('keep')) eWin.appendChild(e);
	}
	
	var embedRegs(default, null):Hash<EmbedReg>;
	var embeddedElements(default, null):Hash<EmbedArgs>;
	var liveEmbeds:NodeList<HTMLEmbedElement>;
	
	public function writeHTML(html:String):Void {
		saveElements();
		element.innerHTML = html;
		_refresh();
	}
	
	public function refresh() {}
	
	var previousActiveEmbeds:Hash<EmbedArgs>;
	
	//use this as an url argument
	public var fallbackIds(default,null):String;
	
	function _refresh() {
		if (element == null) return;
		var t = this;
		var activeEmbeds = new Hash(),embeds=[];
		for (embed in liveEmbeds) embeds.push(embed);
		for (embed in embeds) {
			//ignore 'normal' use of the embed-tag (e.g. for flash)
			if (embed.src != '') continue;
			var id = embed.id;
			if (id == '') embed.nextSibling == null || embed.nextSibling.id == ''?continue:id = embed.nextSibling.id;
			var args = embeddedElements.get(id);
			//first time the embedded element id is encountered
			if (args == null) {
				if (id != embed.id) fallbackIds += (fallbackIds==''?'':'|') + id;
				var reg = embedRegs.get(id);
				var setActive, state = state.createState(function(sA:Bool->Void) setActive = sA );
				//no handler is known for the current id
				if (reg == null) {
					//use the fallback element (nextSibling) when an id was provided
					if (id != '') {
						embeddedElements.set(id, {elm:embed.nextSibling,setActive:setActive});
						embed.parentNode.removeChild(embed);
					}
					continue;
				}
				//remove fallback element
				else if (embed.nextSibling!=null && id == embed.nextSibling.id) embed.parentNode.removeChild(embed.nextSibling);
				args = { elm:element.ownerDocument.createElement(reg.tagName), setActive:setActive };
				embeddedElements.set(id, args);
				args.elm.id = id;
				jstm.Runtime.useClass(reg.className, function(C:Class<Dynamic>) {
					untyped C[reg.methodName](args.elm, t.controller, state);
				});
			}
			//for (a in embed.attributes) args.elm.setAttribute(a.name, a.value);
			activeEmbeds.set(id, args);
			args.setActive(true);
			embed.parentNode.replaceChild(args.elm, embed);
		}
		if (previousActiveEmbeds != null) for (a in previousActiveEmbeds) if (!activeEmbeds.exists(a.elm.id)) a.setActive(false);
		previousActiveEmbeds = activeEmbeds;
	}
	
	//do not use this function directly, use embed instead
	public function _embed(embedId:String, tagName:String, className:String, methodName:String) {
		embedRegs.set(embedId, {tagName:tagName,className:className,methodName:methodName});
		refresh();
	}
	#end
	
	@:macro public function embedElement(eThis:Expr,embedId:ExprRequire<String>,cb:ExprRequire<Dynamic->Dynamic->State->Void>):Expr {
		var p = cb.pos;
		var elmClassName=switch(Context.typeof(cb)) {
			case TFun(args, ret):
				var instType = args[0].t;
				switch(instType) {
					case TInst(a, b): a.toString();
					default:
				}
			default: Context.error('function expected', p);
		}
		var className='',cbClassName = '', cbFnName = '';
		switch(cb.expr) {
			case EField(e, f):
				cbFnName = f;
				switch(e.expr) {
					case EConst(c):
						switch(c) {
							case CType(s):
								className = s;
							default:
						}
					case EType(e, f):
						className = f;
					default: trace(e.expr);
				}
			default: Context.error('function expected', cb.pos);
		}
		switch(Context.follow(Context.getType(className))) {
			case TInst(c, a): cbClassName = c.toString();
			default:
		}
		//trace([embedId,elmClassName.htmlElementClassToTagName(),cbClassName,cbFnName].join(', '));
		return {
			expr:ECall( { expr:EField(eThis,'_embed'), pos:p }, [
				embedId,//{expr:EConst(CString(embedId)),pos:p},
				{expr:EConst(CString(elmClassName.htmlElementClassToTagName())),pos:p},
				{expr:EConst(CString(cbClassName)),pos:p},
				{expr:EConst(CString(cbFnName)),pos:p}
			]),
			pos:p
		};
	}
	/*
	//registers a callback for when embed element with id==embedId is encountered
	@:macro function embed(eThis:Expr,embedId:String,cb:ExprRequire<Control<Dynamic>->Void>):Expr {
		var instTypeName=switch(Context.typeof(cb)) {
			case TFun(args, ret):
				var instType = args[0].t;
				switch(instType) {
					case TInst(a, b): a.toString();
					default://TODO: TInst etc.
				}
			default: Context.error('function expected', cb.pos);
		}
		var p = cb.pos;
		//trace(instTypeName);
		return {
			expr:ECall( { expr:EField(eThis,'_embed'), pos:p }, [
				{expr:EConst(CString(embedId)),pos:p},
				{expr:EConst(CString(instTypeName)),pos:p},
				cb
			]),
			pos:p
		};
	}*/
}